package cloud.tianai.neuron.common.util;

import cloud.tianai.neuron.common.Matrix;
import cloud.tianai.neuron.common.NumPy;

import java.util.List;

/**
 * @Author: 天爱有情
 * @date 2022/12/12 17:28
 * @Description 数据预处理
 */
public class RegressionUtils {
    public static final int NONE_NORMALIZE = -1;

    public static NormalizeData prepareForTraining(Matrix datas, int polynomialDegress, int sinusoidDegress, int normalizeType) {
        return prepareForTraining(datas, null, null, null, null, polynomialDegress, sinusoidDegress, normalizeType);
    }

    public static NormalizeData prepareForTraining(Matrix datas,
                                                   Matrix normalizationWeight1,
                                                   Matrix normalizationWeight2,
                                                   Matrix x2NormalizationWeight1,
                                                   Matrix x2NormalizationWeight2,
                                                   int polynomialDegress, int sinusoidDegress, int normalizeType) {
        Matrix datasProcessed = null;
        Matrix dataNormalized = null;
        if (normalizeType > NONE_NORMALIZE) {
            NormalizeData normalize;
            if (normalizeType == 1) {
                // score
                normalize = zScoreStandard(datas, normalizationWeight1, normalizationWeight2);
            } else {
                // min-max
                normalize = minMaxScaling(datas, normalizationWeight1, normalizationWeight2);
            }
            dataNormalized = normalize.featuresNormalized;
            normalizationWeight1 = normalize.normalizationWeight1;
            normalizationWeight2 = normalize.normalizationWeight2;
            datasProcessed = dataNormalized;
        }
        if (datasProcessed == null) {
//            datasProcessed = NumPy.copy(datas);
            datasProcessed = datas;
            dataNormalized = datasProcessed;
        }

        if (sinusoidDegress > 0) {
            Matrix sinusoids = generateSinusoids(dataNormalized, sinusoidDegress);
            NumPy.concatenate(datasProcessed, sinusoids);

        }
        if (polynomialDegress > 0) {
            NormalizeData normalizeData1 = generatePolynomials(dataNormalized, polynomialDegress, x2NormalizationWeight1, x2NormalizationWeight2, normalizeType);
            Matrix polynomials = normalizeData1.featuresNormalized;
            x2NormalizationWeight1 = normalizeData1.normalizationWeight1;
            x2NormalizationWeight2 = normalizeData1.normalizationWeight2;
            NumPy.concatenate(datasProcessed, polynomials);
        }
        NormalizeData result = new NormalizeData();
        result.normalizationWeight1 = normalizationWeight1;
        result.normalizationWeight2 = normalizationWeight2;
        result.featuresNormalized = datasProcessed;
        result.x2NormalizationWeight1 = x2NormalizationWeight1;
        result.x2NormalizationWeight2 = x2NormalizationWeight2;
        return result;
    }


    public static NormalizeData generatePolynomials(Matrix dataSet, int polynomialDegree, int normalizeType) {
        return generatePolynomials(dataSet, polynomialDegree, null, null, normalizeType);
    }

    /**
     * 生成多项式
     *
     * @param dataSet
     * @param polynomialDegree
     * @param x2NormalizationWeight1
     * @param x2NormalizationWeight2
     * @param normalizeType
     * @return
     */
    public static NormalizeData generatePolynomials(Matrix dataSet,
                                                    int polynomialDegree,
                                                    Matrix x2NormalizationWeight1,
                                                    Matrix x2NormalizationWeight2,
                                                    int normalizeType) {
        List<Matrix> splitArray = NumPy.split(dataSet, 2);
        Matrix dataSet1 = splitArray.get(0);
        Matrix dataSet2 = splitArray.get(1);
        if (dataSet1.getY() == 0) {
            dataSet1 = dataSet2;
        } else if (dataSet2.getY() == 0) {
            dataSet2 = dataSet1;
        }
        int numFeatures = dataSet1.getY() < dataSet2.getX() ? dataSet1.getY() : dataSet2.getY();

//        ArrayList[] polynomials = drowList(dataSet1.length);
        Matrix polynomials = new Matrix(dataSet1.getX(), 0);
        for (int i = 1; i < polynomialDegree + 1; i++) {
            for (int a = 0; a < i + 1; a++) {
                Matrix dot = dataSet1.pow(i - a).multiply(dataSet2.pow(a));
                NumPy.concatenate(polynomials, dot);
            }
        }
        // 标准化
        if (normalizeType > NONE_NORMALIZE) {
            if (normalizeType == 1) {
                // score
                return zScoreStandard(polynomials, x2NormalizationWeight1, x2NormalizationWeight2);
            } else {
                // min-max
                return minMaxScaling(polynomials, x2NormalizationWeight1, x2NormalizationWeight2);
            }
        }
        NormalizeData normalizeData1 = new NormalizeData();
        normalizeData1.featuresNormalized = polynomials;
        normalizeData1.normalizationWeight1 = x2NormalizationWeight1;
        normalizeData1.normalizationWeight2 = x2NormalizationWeight2;
        normalizeData1.x2NormalizationWeight1 = x2NormalizationWeight1;
        normalizeData1.x2NormalizationWeight2 = x2NormalizationWeight2;
        return normalizeData1;

    }

    /**
     * 将数据进行 sin变换
     *
     * @param dataSet
     * @param sinusoidDegress
     * @return
     */
    public static Matrix generateSinusoids(Matrix dataSet, int sinusoidDegress) {
        Matrix results = new Matrix(dataSet.size(), 0);
        for (int i = 0; i < sinusoidDegress; i++) {
            Matrix dot = dataSet.multiply(i + 1);
            Matrix sin = dot.sin();
            NumPy.concatenate(results, sin);
        }
        return results;
    }


    public static NormalizeData zScoreStandard(Matrix features) {
        return zScoreStandard(features, null, null);
    }

    /**
     * 均值标准化
     * z = (x-mean)/std
     * 该种归一化方式要求原始数据的分布可以近似为高斯分布，否则归一化的效果会变得很糟糕。
     * <p>
     * 在分类、聚类算法中，需要使用距离来度量相似性的时候、或者使用PCA技术进行降维的时候，方法(Z-score standardization)表现更好
     *
     * @param features
     * @param featuresMean
     * @param featuresDeviation
     * @return
     */
    public static NormalizeData zScoreStandard(Matrix features, Matrix featuresMean, Matrix featuresDeviation) {
        Matrix featuresNormalized = NumPy.copy(features);
        if (featuresMean == null) {
            featuresMean = featuresNormalized.mean(0);
        }
        if (featuresDeviation == null) {
            featuresDeviation = featuresNormalized.std(0);
        }
        featuresNormalized = featuresNormalized.sub(featuresMean);
        featuresNormalized = featuresNormalized.divide(featuresDeviation, true);
        NormalizeData normalizeData = new NormalizeData();
        normalizeData.featuresNormalized = featuresNormalized;
        normalizeData.normalizationWeight1 = featuresMean;
        normalizeData.normalizationWeight2 = featuresDeviation;
        return normalizeData;
    }

    /**
     * 在不涉及距离度量、协方差计算、数据不符合正太分布的时候，可以使用第一种方法或其他归一化方法。比如图像处理中，将RGB图像转换为灰度图像后将其值限定在[0 255]的范围。
     * 线性函数归一化(Min-Max scaling)
     * x = (x - min) / (max - min)
     *
     * @param features
     * @param minMatrix
     * @param maxMatrix
     * @return
     */
    public static NormalizeData minMaxScaling(Matrix features, Matrix minMatrix, Matrix maxMatrix) {
        // 归一化
        Matrix featuresNormalized = NumPy.copy(features);
        if (minMatrix == null || maxMatrix == null) {
            Matrix matrix = featuresNormalized.minMax(0);
            minMatrix = matrix.getToMatrix(0);
            maxMatrix = matrix.getToMatrix(1);
        }
        for (int x = 0; x < featuresNormalized.size(); x++) {
            for (int y = 0; y < featuresNormalized.get(x).size(); y++) {
                Double aDouble = featuresNormalized.get(x, y);
                double a = aDouble - minMatrix.get(0, y);
                double b = (maxMatrix.get(0, y) - minMatrix.get(0, y));
                if (b == 0) {
                    b = 1;
                }
                featuresNormalized.set(x, y, a / b);
            }
        }

        NormalizeData normalizeData = new NormalizeData();
        normalizeData.featuresNormalized = featuresNormalized;
        normalizeData.normalizationWeight1 = minMatrix;
        normalizeData.normalizationWeight2 = maxMatrix;
        return normalizeData;
    }

}
